'''
Created on 2022/04/24

@author: shenchh
'''
import sys, os, re
import json

tab_space = "    "
banned_word_en = ['SW', 'CAN', 'switch', 'Switch', 'meter', 'Meter', 'aircon', 'Aircon', 'button', 'Button']
banned_word_jp = ['スイッチ', 'メータ', 'ボタン', 'エアコン', '０', '１', '２', '３', '４', '５', '６', '７', '８', '９']


def pascal_case_to_snake_case(pascal_str: str):
    res = []
    for index, char in enumerate(pascal_str):
        if char.isupper() and index != 0:
            if pascal_str[index - 1].islower():
                res.append("_")
            else:
                if (index + 1) < len(pascal_str):
                    if pascal_str[index + 1].islower():
                        res.append("_")
        res.append(char)

    return ''.join(res)


# 1：大文字と小文字が一致しない問題
#     機能概要説明中の英単語の最初の1文字を大文字へ変換した後、すべての設定項目とenum名を検索し、大文字と小文字の構成が異なるの項目があれば、エラーを出力する。
#
#     例：    設定項目：VehicleSettings/Door/UnLock/UnLockByShift/Featureの場合、機能概要説明の内容が「Shift operation interlocking unlock」、
#         四つ英単語「Shift」「Operation」「Interlocking」「Unlock」を検索する、
#         「Unlock」で検索するときに、設定項目の三階層目と四階層目の「UnLock」と「UnLockByShift」中”l”が”L”になってますので、
#         一致しないと認識されて、エラーを出力する。
#
# param word_dict={string:word_list}
def check_word_consistency(word_dict, fh_w):
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}大文字と小文字が一致しない問題を確認する\n')
    fh_w.write(f'{tab_space}=======================================================\n')
    err_msg_dict = {}
    for name, word_list in word_dict.items():
        for layer in range(len(word_list)):
            word = word_list[layer]
            if err_msg_dict.get(word, None) is None:
                err_msg_dict[word] = []
            for tmp_name, tmp_word_list in word_dict.items():
                for tmp_layer in range(len(tmp_word_list)):
                    tmp_word = tmp_word_list[tmp_layer]
                    if word.upper() == tmp_word.upper() and word != tmp_word:
                        err_msg_dict[word].append(f'{tab_space}{tab_space}{tmp_name}=>layer={tmp_layer},word={tmp_word}\n')
                        continue
    # write file
    for word, err_msg_list in err_msg_dict.items():
        if len(err_msg_list) > 0:
            fh_w.write(f'{tab_space}The word={word}:\n')
            for err_path in err_msg_list:
                fh_w.write(err_path)

    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}最初の一文字が小文字である問題を確認する\n')
    fh_w.write(f'{tab_space}=======================================================\n')
    err_msg_dict = {}
    for name, word_list in word_dict.items():
        for layer in range(len(word_list)):
            word = word_list[layer]
            if word[0:1].islower():
                fh_w.write(f'{tab_space}{tmp_name}=>layer={tmp_layer},word={tmp_word}\n')



# 2：階層構成の誤り
# 2-1 階層数が5 or 6以外の場合、エラーを出力する。
#
#     例：    誤：VehicleSettings/HUD/HeadUpDisplaySettingHeadUpDisplaySetting/AcceptInfo（四階層）
#         正：VehicleSettings/HUD/HeadUpDisplaySetting/HeadUpDisplaySetting/AcceptInfo
#
# 2-2 階層数が6の場合、5階層名の名前が"AddInfo"ではない場合、データを出力する。
#
#     例：    誤：VehicleSettings/UserAuxiliary/InfoSecurity/DigitalKeyRegisterState/AcceptInfo/State
#         正：VehicleSettings/UserAuxiliary/InfoSecurity/DigitalKeyRegisterState/AddInfo/State
#
# param:path_dict={path:dir_name_list}
def check_path_layer(path_dict, fh_w):
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}階層数を確認する.\n')
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}The layer count 1~4:\n')
    for path, dir_name_list in path_dict.items():
        if len(dir_name_list) < 5:
            fh_w.write(f'{tab_space}{tab_space}{path}\n')

    fh_w.write(f'{tab_space}The layer count >6:\n')
    for path, dir_name_list in path_dict.items():
        if len(dir_name_list) > 6:
            fh_w.write(f'{tab_space}{tab_space}{path}\n')

    fh_w.write(f'{tab_space}The layer count 6 and five layer is not AddInfo :\n')
    for path, dir_name_list in path_dict.items():
        dir_name_list_len = len(dir_name_list)
        if dir_name_list_len == 6 and dir_name_list[4] != "AddInfo":
            fh_w.write(f'{tab_space}{tab_space}{path}\n')


def check_path_name(path_dict, fh_w):
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}パス名の大文字化チェック.\n')
    fh_w.write(f'{tab_space}=======================================================\n')
    for path, dir_name_list in path_dict.items():
        for dir_name in dir_name_list:
            if dir_name[0].islower():
                fh_w.write(f'{tab_space}{tab_space}{dir_name}\n')





# ３：説明内容の誤り
#     日本語の機能説明中に英数字存在する場合、英語説明および設定項目名に同じような単語がない場合、エラーを出力する。
#
#         例：    設定項目VehicleSettings/TirePressure/TirePressureSetting/FLID2TireIndicateInformation/Featureの説明内容、
#             日本語：空気圧モニタ FR輪(ID2) 圧力値 点灯・非点灯 ；英語：FL(ID2) Tire indicate information；
#             FLとFRが一致しない。
#
#         例：VehicleSettings/TirePressure/TirePressureSetting/TirePressureSettingRear3/Featureの説明内容、
#             日本語：タイヤ空気圧設定(リヤ2)の利用可否；英語：Feature of Tire pressure setting (rear 2)
#             日本語説明の2と設定項目名の3が一致しない。
# param memo_dict={string:[string .. string]}
def check_numeric_character(memo_dict, fh_w):
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}説明内容の誤りを確認する.\n')
    fh_w.write(f'{tab_space}{tab_space}1.英数字の設定が一致しない.\n')
    fh_w.write(f'{tab_space}=======================================================\n')

    for name, val_list in memo_dict.items():
        flag = False
        for index_i in range(len(val_list)):
            tmp_val_i = re.sub("\D", "", val_list[index_i])
            for index_j in range(index_i + 1, len(val_list)):
                tmp_val_j = re.sub("\D", "", val_list[index_j])
                if tmp_val_i != tmp_val_j:
                    flag = True
        if flag:
            fh_w.write(f'{tab_space}{tab_space}{val_list}\n')


# 他：使用禁止単語
# 設定項目名・説明内容にSW/Switch/Meterなど変更可否判断用の単語を使用している項目の出力。
# チェック単語：SW/Switch/CAN/meter/aircon
#
# param word_dict={string:word_list}
def check_banned_word(word_dict, fh_w):
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}使用禁止単語を確認する.\n')
    fh_w.write(f'{tab_space}=======================================================\n')
    for key_word in banned_word_en:
        fh_w.write(f'{tab_space}The banned word: {key_word}\n')
        for word_str, word_list in word_dict.items():
            err_flag = False
            for word in word_list:
                if key_word in word:
                    err_flag = True
                    break
            if err_flag:
                fh_w.write(f'{tab_space}{tab_space}{word_str}\n')

    for key_word in banned_word_jp:
        fh_w.write(f'{tab_space}The banned word: {key_word}\n')
        for word_str, word_list in word_dict.items():
            err_flag = False
            for word in word_list:
                if key_word in word:
                    err_flag = True
                    break
            if err_flag:
                fh_w.write(f'{tab_space}{tab_space}{word_str}\n')


def check_json(json_file, fh_w):
    # read file
    with open(json_file, mode = 'r', encoding = 'utf-8') as fh_r:
        input_dict = json.load(fh_r)

    # get name list
    anonymous_list = input_dict.get("anonymous", None)
    if anonymous_list is not None:
        fh_w.write(f'File Path:{json_file}\n')
        # 4：重複項目
        # 設定項目内容の重複が場合、エラーを出力する。
        #     例：設定項目「AAA/BBB/CCC/DDD/」が二つ以上存在する場合、エラーを出力する。
        fh_w.write(f'{tab_space}=======================================================\n')
        fh_w.write(f'{tab_space}重複項目「name」を確認する.\n')
        fh_w.write(f'{tab_space}=======================================================\n')
        anonymous_name_dict = {}
        for item in anonymous_list:
            if anonymous_name_dict.get(item["name"], None):
                fh_w.write(f'{tab_space}{item["name"]}\n')
            else:
                anonymous_name_dict[item["name"]] = item["name"].split("/")

        # check banned word
        check_banned_word(anonymous_name_dict, fh_w)
        # check word consistency
        check_word_consistency(anonymous_name_dict, fh_w)
        # check path layer
        check_path_layer(anonymous_name_dict, fh_w)
        # check path name
        check_path_name(anonymous_name_dict, fh_w)
        fh_w.write('\n')


def check_md(md_file, fh_w):
    # read file
    lines = []
    pattern_line = re.compile(r'.*\n')
    with open(md_file, mode = 'r', encoding = 'utf-8', errors = 'ignore', newline = '\n') as fh_r:
        file_content = fh_r.read() + '\n'
        # linesカウント
        for m in re.finditer(pattern_line, file_content):
            lines.append(m.group())

    fh_w.write(f'File Path:{md_file}\n')
    # 4:重複項目
    # 設定項目内容の重複が場合、エラーを出力する。
    #     例：設定項目「AAA/BBB/CCC/DDD/」が二つ以上存在する場合、エラーを出力する。
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}重複項目「Setting Name」と「Description」を確認する.\n')
    fh_w.write(f'{tab_space}=======================================================\n')
    table_pattern = re.compile('^\| (\".*)\|$', re.I)
    line_dict = {}
    for line in lines:
        match_obj = re.search(table_pattern, line)
        if match_obj:
            line_dict[line] = match_obj.group(1).split('|')

    fh_w.write(f'{tab_space}-------------------------\n{tab_space}Setting Name:\n{tab_space}-------------------------\n')
    path_dict = {}
    for line_list in line_dict.values():
        path = line_list[0].strip()[1:-1]
        if path_dict.get(path, None):
            fh_w.write(f'{tab_space}{tab_space}{path}\n')
        else:
            path_dict[path] = path.split("/")

    fh_w.write(f'{tab_space}-------------------------\n{tab_space}Description:\n{tab_space}-------------------------\n')
    memo_dict = {}
    desc_dict = {}
    desc_link_dict = {}
    desc_pattern = re.compile('^(.*)<br \/>.*\[.*\]\(\.\.\/(.*)\/\).*', re.I)
    for line_list in line_dict.values():
        path = line_list[0].strip()[1:-1]
        desc = line_list[4].strip()
        match_obj = re.search(desc_pattern, desc)
        if match_obj:
            g1 = match_obj.group(1)
            g2 = match_obj.group(2)
            memo_dict[f'{path}'] = [path, g1, g2]
            if desc_dict.get(g1, None):
                fh_w.write(f'{tab_space}{tab_space}{desc}\n')
            else:
                desc_dict[g1] = g1.split(" ")
                desc_link_dict[g2] = g2.split("_")

    # check banned word
    tmp_dict = dict(list(path_dict.items()) + list(desc_dict.items()) + list(desc_link_dict.items()))
    check_banned_word(tmp_dict, fh_w)

    # check word consistency
    tmp_dict = dict(list(path_dict.items()) + list(desc_link_dict.items()))
    check_word_consistency(tmp_dict, fh_w)

    # check path layer
    check_path_layer(path_dict, fh_w)
    # check path name
    check_path_name(path_dict, fh_w)

    # check numeric character
    check_numeric_character(memo_dict, fh_w)

    fh_w.write('\n')


def check_head(head_file, fh_w):
    fh_w.write(f'File Path:{head_file}\n')
    # read file
    lines = []
    pattern_line = re.compile(r'.*\n')
    with open(head_file, mode = 'r', encoding = 'utf-8', errors = 'ignore', newline = '\n') as fh_r:
        file_content = fh_r.read() + '\n'
        # linesカウント
        for m in re.finditer(pattern_line, file_content):
            lines.append(m.group())

    # 4:重複項目
    # 設定項目内容の重複が場合、エラーを出力する。
    #     例：設定項目「AAA/BBB/CCC/DDD/」が二つ以上存在する場合、エラーを出力する。
    repeat_flag = False
    enum_member_err_flag = False

    enum_name_dict = {}
    enum_member_dict = {}
    parent_enum_nam = re.compile('^enum class (.*) {$', re.I)
    for line_no in range(len(lines)):
        line = lines[line_no]
        match_obj = re.search(parent_enum_nam, line.strip())
        if match_obj:
            enum_name = match_obj.group(1)
            if enum_name_dict.get(enum_name, None):
                if repeat_flag == False:
                    repeat_flag = True
                    fh_w.write(f'{tab_space}=======================================================\n')
                    fh_w.write(f'{tab_space}重複項目「enum Name」を確認する.\n')
                    fh_w.write(f'{tab_space}=======================================================\n')
                fh_w.write(f'{tab_space}{enum_name}\n')
            else:
                enum_name_dict[enum_name] = pascal_case_to_snake_case(enum_name).split("_")
        elif line.strip() == "};":
            enum_name = ""
        else:
            line_strip = line.strip()
            if line_strip == "":
                continue
            elif line_strip.startswith("K_"):
                enum_member_dict[f'{enum_name}.{line.strip()}'] = []
                tmp_list = line_strip[2:].split("_")
                for tmp in tmp_list:
                    if tmp != "SW":
                        enum_member_dict[f'{enum_name}.{line_strip}'].append(f'{tmp[0:1].upper()}{tmp[1:].lower()}')
                    else:
                        enum_member_dict[f'{enum_name}.{line_strip}'].append(tmp)
            else:
                if enum_member_err_flag == False:
                    enum_member_err_flag = True
                    fh_w.write(f'{tab_space}=======================================================\n')
                    fh_w.write(f'{tab_space}The first letter of the enum' + " member's name is not K_.\n")
                    fh_w.write(f'{tab_space}=======================================================\n')
                fh_w.write(f'{tab_space}{enum_name}.{line_strip}\n')

    # check banned word
    check_banned_word(enum_name_dict, fh_w)
    check_banned_word(enum_member_dict, fh_w)

    # check word consistency
    check_word_consistency(enum_name_dict, fh_w)
    check_word_consistency(enum_member_dict, fh_w)

def check_capnp(capnp_file, fh_w):
    # read file
    lines = []
    capnp_line = re.compile(r'.*\n')
    with open(capnp_file, mode = 'r', encoding = 'utf-8', errors = 'ignore', newline = '\n') as fh_r:
        file_content = fh_r.read() + '\n'
        # linesカウント
        for m in re.finditer(capnp_line, file_content):
            lines.append(m.group())

    fh_w.write(f'File Path:{capnp_file}\n')
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}無効なデータを確認してください「無効な列挙型データ」.\n')
    fh_w.write(f'{tab_space}=======================================================\n')

    enum_pattern = re.compile('enum (.*) {$', re.I)
    enum_dict = {}
    notes_pattern = re.compile('# (.*)$', re.I)
    notes_dict = {}
    member_pattern = re.compile('k(.*)$', re.I)
    member_dict = {}
    for line in lines:
        linetmp = line.strip()
        match_enum = re.match(enum_pattern, linetmp)
        match_notes = re.match(notes_pattern, linetmp)
        match_member = re.match(member_pattern, linetmp)
        if match_enum:
            enum_dict[linetmp] = match_enum.group(1)
        elif match_notes:
            notes_dict[linetmp] = match_notes.group(1)
        elif match_member:
            member_dict[linetmp] = match_member.group()
        elif linetmp == "}":
            pass
        elif linetmp == "":
            pass
        else:
            fh_w.write(f'{tab_space}{tab_space}{linetmp}\n')

    # 4:重複項目
    # 設定項目内容の重複が場合、エラーを出力する。
    #     例：設定項目「AAA/BBB/CCC/DDD/」が二つ以上存在する場合、エラーを出力する。
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}重複項目「Enum Name」と「Notes」と「Member」を確認する.\n')
    fh_w.write(f'{tab_space}=======================================================\n')
    fh_w.write(f'{tab_space}-------------------------\n{tab_space}Enum Name:\n{tab_space}-------------------------\n')
    enum_data_dict = {}
    for enum_list in enum_dict.values():
        if enum_data_dict.get(enum_list, None):
            fh_w.write(f'{tab_space}{tab_space}{enum_list}\n')
        else:
            enum_data_dict[enum_list] = pascal_case_to_snake_case(enum_list).split("_")

    fh_w.write(f'{tab_space}-------------------------\n{tab_space}Notes:\n{tab_space}-------------------------\n')
    notes_data_pattern = re.compile('(.*)<br />(.*)', re.I)
    notes_data_dict = {}
    for notes_list in notes_dict.values():
        if notes_data_dict.get(notes_list, None):
            fh_w.write(f'{tab_space}{tab_space}{notes_list}\n')
        else:
            match_notes_data = re.match(notes_data_pattern, notes_list)
            if match_notes_data:
                strtmp = notes_list.split("<br />")
                if notes_data_dict.get(strtmp[0], None):
                    fh_w.write(f'{tab_space}{tab_space}{strtmp[0]}\n')
                else:
                    notes_data_dict[strtmp[0]] = strtmp[0].split("/")
                if notes_data_dict.get(strtmp[1], None):
                    fh_w.write(f'{tab_space}{tab_space}{strtmp[1]}\n')
                else:
                    notes_data_dict[strtmp[1]] = strtmp[1].split("/")
            else:
                notes_data_dict[notes_list] = notes_list.split("/")

    fh_w.write(f'{tab_space}-------------------------\n{tab_space}Member:\n{tab_space}-------------------------\n')
    member_data_pattern = re.compile('k(.*)\s+@\d+;\s+#\s~/english\s(.*)\s+~/japanese\s(.*)', re.I)
    member_data1_dict = {}
    member_data2_dict = {}
    member_data3_dict = {}
    member_data_dict = {}
    for member_list in member_dict.values():
        if member_data_dict.get(member_list, None):
            fh_w.write(f'{tab_space}{tab_space}{member_list}\n')
        else:
            match_member_data = re.match(member_data_pattern, member_list)
            if match_member_data:
                data1 = match_member_data.group(1)
                data2 = match_member_data.group(2)
                data3 = match_member_data.group(3)
                member_data1_dict[data1] = pascal_case_to_snake_case(data1).split("_")
                member_data2_dict[data2] = data2.split(" ")
                member_data3_dict[data3] = data3.split(" ")
                member_data_dict[member_list] = [match_member_data.group(1),match_member_data.group(2),match_member_data.group(3)]
            else:
                fh_w.write(f'{tab_space}Error{tab_space}{member_list}\n')

    # check banned word
    check_banned_word(enum_data_dict, fh_w)
    check_banned_word(notes_data_dict, fh_w)
    check_banned_word(member_data1_dict, fh_w)
    check_banned_word(member_data2_dict, fh_w)
    check_banned_word(member_data3_dict, fh_w)
    #check_banned_word(member_data_dict, fh_w)

    # check word consistency
    check_word_consistency(enum_data_dict, fh_w)
    check_word_consistency(member_data1_dict, fh_w)

    # check path layer
    check_path_layer(notes_data_dict, fh_w)
    # check path name
    check_path_name(notes_data_dict, fh_w)

    # check numeric character
    check_numeric_character(member_data_dict, fh_w)

    fh_w.write('\n')



if __name__ == '__main__':
    retry = 0
    while True:
        input_path = input('Please input file path = ').strip()
        # input_path = './header'
        if retry > 3:
            print(f'error exit.')
            sys.exit(1)
        elif not os.path.exists(input_path):
            retry += 1
            print(f'{input_path} is not exits.')
            continue
        else:
            break

    ##########################
    md_check_result = "./md_check_result.log"
    json_check_result = "./json_check_result.log"
    header_check_result = "./header_check_result.log"
    capnp_check_result = "./capnp_check_result.log"
    file_name_check_result = f'./file_name_check_result.log'
    try:
        json_fh_w = open(json_check_result, mode = 'w', encoding = 'utf-8')
        md_fh_w = open(md_check_result, mode = 'w', encoding = 'utf-8')
        header_fh_w = open(header_check_result, mode = 'w', encoding = 'utf-8')
        capnp_fh_w = open(capnp_check_result, mode = 'w', encoding = 'utf-8')
        if os.path.isdir(input_path):
            file_name_dict = {}
            for path, dirs, files in os.walk(input_path):
                for file in files:
                    file_name_dict[file] = file.split('_')

                    input_file_path = os.path.join(path, file)
                    if input_file_path.endswith('.json'):
                        check_json(input_file_path, json_fh_w)
                    elif input_file_path.endswith(".md"):
                        check_md(input_file_path, md_fh_w)
                    elif input_file_path.endswith(".h"):
                        check_head(input_file_path, header_fh_w)
                    elif input_file_path.endswith(".capnp"):
                        check_capnp(input_file_path, capnp_fh_w)
                    else:
                        pass

            # cehck file name
            with open(file_name_check_result, mode = 'w', encoding = 'utf-8') as fh_w:
                check_word_consistency(file_name_dict, fh_w)
        else:
            input_file_path=input_path
            if input_file_path.endswith('.json'):
                check_json(input_file_path, json_fh_w)
            elif input_file_path.endswith(".md"):
                check_md(input_file_path, md_fh_w)
            elif input_file_path.endswith(".h"):
                check_capnp(input_file_path, header_fh_w)
            else:
                pass
    except OSError as err:
        print("OS error: {0}".format(err))
    except ValueError:
        print("Could not convert data to an integer.")
    except:
        print("Unexpected error:", sys.exc_info()[0])
        raise

    # check file name

